package com.jzo2o.orders.manager.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.NumberUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.client.CustomersClient;
import com.jzo2o.orders.manager.service.client.FoundationsClient;
import com.jzo2o.redis.annotations.Lock;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
    @Resource
    private FoundationsClient foundationsClient;
    @Resource
    private CustomersClient customersClient;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private NativePayApi nativePayApi;
    @Resource
    private TradingApi tradingApi;
    @Resource
    private TradeProperties tradeProperties;
    // 注入自己本身，解决循环依赖问题
    @Resource
    private IOrdersCreateService owner;
    /**
     * 用户预约请求下单
     *
     * @param placeOrderReqDTO 请求实体
     * @return
     */
    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
        // . 参数校验
        if(ObjectUtils.isNull(placeOrderReqDTO)){
            throw new CommonException("参数为空");
        }
        Long userId = UserContext.currentUserId();
        if(ObjectUtils.isNull(userId)){
            throw new CommonException("用户未认证");
        }
        String serveStartTime = DateUtils.format(placeOrderReqDTO.getServeStartTime(), "yyyyMMddHHmm");
        return owner.placeLockOrder(placeOrderReqDTO, userId, serveStartTime);

    }

    /**
     * 用户预约请求下单：解决订单重复提交问题
     * 利用分布式锁实现订单提交防抖问题
     * @param placeOrderReqDTO 请求实体
     * @param userId
     * @param serveStartTime
     * @return
     */
    @Override
    @Lock(formatter = "ORDERS:CREATE:LOCK:#{userId}:#{placeOrderReqDTO.serveId}:#{serveStartTime}", time = 10, waitTime = 5, unlock=false)
    public PlaceOrderResDTO placeLockOrder(PlaceOrderReqDTO placeOrderReqDTO, Long userId, String serveStartTime) {
        // 2. 根据服务id查询服务是否存在
        ServeAggregationResDTO serveAggregationResDTO = foundationsClient.findById(placeOrderReqDTO.getServeId());
        if(ObjectUtils.isNotNull(serveAggregationResDTO) && serveAggregationResDTO.getId() < 0){
            throw new CommonException("服务暂时忙，请稍后重试...");
        }
        if(serveAggregationResDTO.getSaleStatus() != 2){
            throw new CommonException("服务未上架");
        }
        // 3. 根据地址id查询地址是否存在
        AddressBookResDTO addressBookResDTO = customersClient.getDetailById(placeOrderReqDTO.getAddressBookId());
        if(ObjectUtils.isNotNull(addressBookResDTO) && serveAggregationResDTO.getId() < 0){
            throw new CommonException("服务暂时忙，请稍后重试...");
        }
        // 4. 补全订单实体
        Orders orders = new Orders();
        // 订单id生成规则：1位业务类型+6位年月日+12位随机数
        Long ordersId = generateOrdersId();
        orders.setId(ordersId);
        orders.setUserId(UserContext.currentUserId());
        orders.setServeTypeId(serveAggregationResDTO.getServeTypeId());
        orders.setServeTypeName(serveAggregationResDTO.getServeTypeName());
        orders.setServeItemId(serveAggregationResDTO.getServeItemId());
        orders.setServeItemName(serveAggregationResDTO.getServeItemName());
        orders.setServeItemImg(serveAggregationResDTO.getServeItemImg());
        orders.setUnit(serveAggregationResDTO.getUnit());
        orders.setServeId(serveAggregationResDTO.getId());
        orders.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());
        orders.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        orders.setPrice(serveAggregationResDTO.getPrice());
        orders.setPurNum(placeOrderReqDTO.getPurNum());
        orders.setTotalAmount(serveAggregationResDTO.getPrice().multiply(new BigDecimal(placeOrderReqDTO.getPurNum())));
        // TODO 优惠金额计算
        orders.setDiscountAmount(new BigDecimal("0"));
        orders.setRealPayAmount((NumberUtils.sub(orders.getTotalAmount(), orders.getDiscountAmount())));
        orders.setCityCode(serveAggregationResDTO.getCityCode());
        orders.setServeAddress(addressBookResDTO.getAddress());
        orders.setContactsPhone(addressBookResDTO.getPhone());
        orders.setContactsName(addressBookResDTO.getName());
        orders.setServeStartTime(placeOrderReqDTO.getServeStartTime());

        orders.setDisplay(1);
        orders.setSortBy(DateUtils.toEpochMilli(LocalDateTime.now()) + orders.getId() % 100000);
        // 5. 新增订单
        // 如果已这种方式插入订单，事务方法持有数据库连接资源时间过长，因为存在远程接口调用
        // this.save(orders);
        // 单独抽取事务方法，控制资源持有时间问题，注意事务失效（如果直接调用本类中的事务方法，
        // 会导致事务失效：原因spring事务是基于apo+动态代理实现的，没有接口无法实现代理）
        owner.saveOrders(orders);
        return new PlaceOrderResDTO(orders.getId());
    }

    /**
     * 订单支付
     *
     * @param id              订单id
     * @param ordersPayReqDTO 支付请求实体
     * @return
     */
    @Override
    @GlobalTransactional
    public OrdersPayResDTO pay(Long id, OrdersPayReqDTO ordersPayReqDTO) {
        // 1. 判断订单是否存在
        Orders orders = this.getById(id);
        if(ObjectUtils.isNull(orders)) {
            throw new CommonException("订单不存在");
        }
        // 2. 判断订单是否已支付
        if(orders.getPayStatus().equals(OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                && ObjectUtils.isNotNull(orders.getTradingOrderNo())
                && ObjectUtils.isNotNull(orders.getTransactionId()) ){
            throw new CommonException("订单已支付,无需重复操作");
        }
        // 3. 未支付订单调用支付服务创建交易单及三方支付单
        NativePayReqDTO nativePayReqDTO = new NativePayReqDTO();
        PayChannelEnum reqTradingChannel = ordersPayReqDTO.getTradingChannel();
        PayChannelEnum tradingChannel = ObjectUtils.equals(reqTradingChannel, PayChannelEnum.ALI_PAY) ? PayChannelEnum.ALI_PAY : PayChannelEnum.WECHAT_PAY;
        Long enterpriseId = ObjectUtils.equals(reqTradingChannel, PayChannelEnum.ALI_PAY) ? tradeProperties.getAliEnterpriseId() : tradeProperties.getWechatEnterpriseId();
        nativePayReqDTO.setEnterpriseId(enterpriseId);
        nativePayReqDTO.setProductAppId("jzo2o-orders");
        nativePayReqDTO.setProductOrderNo(orders.getId());
        nativePayReqDTO.setTradingChannel(tradingChannel);
        nativePayReqDTO.setTradingAmount(orders.getRealPayAmount());
        nativePayReqDTO.setMemo(orders.getServeItemName());
        // 订单支付渠道变更
        if(ObjectUtils.isNotNull(reqTradingChannel) && ObjectUtils.notEqual(reqTradingChannel, orders.getTradingChannel())){
            nativePayReqDTO.setChangeChannel(true);
        }
        NativePayResDTO nativePayResDTO = nativePayApi.createDownLineTrading(nativePayReqDTO);
        // 4. 更新订单支付信息
        boolean flag = this.lambdaUpdate()
                .eq(Orders::getId, id)
                .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())
                .set(Orders::getTradingChannel, nativePayResDTO.getTradingChannel())
                .update();
        if(!flag){
            throw new CommonException("订单支付失败");
        }
        // 5. 构建支付返回结果
        return BeanUtils.toBean(nativePayResDTO, OrdersPayResDTO.class);
    }

    /**
     * 订单支付结果查询
     *
     * @param id 订单id
     * @return
     */
    @Override
    public OrdersPayResDTO payResult(Long id) {
        // 1. 判断订单是否存在
        Orders orders = this.getById(id);
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("订单不存在");
        }
        OrdersPayResDTO ordersPayResDTO = new OrdersPayResDTO();
        ordersPayResDTO.setProductOrderNo(id);
        ordersPayResDTO.setTradingOrderNo(orders.getTradingOrderNo());
        ordersPayResDTO.setTradingChannel(orders.getTradingChannel());
        // 2. 判断订单是否已支付
        if( ObjectUtils.equals(orders.getPayStatus(), OrderPayStatusEnum.PAY_SUCCESS.getStatus())
            && ObjectUtils.isNotNull(orders.getTradingOrderNo())
            && ObjectUtils.isNotNull(orders.getTransactionId())
        ){
            // 已支付订单直接返回结果
            ordersPayResDTO.setPayStatus(orders.getPayStatus());
            return ordersPayResDTO;
        }
        // 3. 未支付订单，主动调用支付服务查询支付结果
        TradingResDTO tradingResDTO = tradingApi.findTradResultByTradingOrderNo(orders.getTradingOrderNo());
        if(tradingResDTO.getTradingState() == TradingStateEnum.YJS){
            // 支付成功订单
            TradeStatusMsg tradeStatusMsg = new TradeStatusMsg();
            tradeStatusMsg.setTradingOrderNo(tradingResDTO.getTradingOrderNo());
            tradeStatusMsg.setProductOrderNo(id);
            tradeStatusMsg.setTransactionId(tradingResDTO.getTransactionId());
            tradeStatusMsg.setTradingChannel(tradingResDTO.getTradingChannel());
            // 更新支付成功后的订单信息
            owner.paySuccess(tradeStatusMsg);
            // 构造响应结果
            ordersPayResDTO.setTradingChannel(tradingResDTO.getTradingChannel());
            ordersPayResDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
            return ordersPayResDTO;
        }else {
            // 支付结果查询不等于已结算，直接返回订单支付状态
            ordersPayResDTO.setPayStatus(orders.getPayStatus());
            return ordersPayResDTO;
        }
    }

    /**
     * 支付成功， 更新数据库的订单表及其他信息
     *
     * @param tradeStatusMsg 交易状态消息
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void paySuccess(TradeStatusMsg tradeStatusMsg) {
        // 1. 判断订单是否存在
        Orders orders = this.getById(tradeStatusMsg.getProductOrderNo());
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("订单不存在");
        }
        // 2. 判断订单支付状态是否是已支付
        if (ObjectUtils.equals(orders.getPayStatus(), OrderPayStatusEnum.PAY_SUCCESS.getStatus())) {
            throw new CommonException("已支付订单，支付状态更改失败");
        }
        // 3. 判断订单状态是否是待支付
        if(ObjectUtils.notEqual(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())){
            throw new CommonException("订单状态不是待支付，支付状态更改失败");
        }
        // 4. 判断是否第三方交易号是否存在
        if(ObjectUtils.isNull(tradeStatusMsg.getTransactionId())){
            throw new CommonException("三方交易号不存在，支付状态更改失败");
        }
        // 2. 更改订单信息
        boolean flag = this.lambdaUpdate()
                .eq(Orders::getId, tradeStatusMsg.getProductOrderNo())
                .set(Orders::getOrdersStatus, OrderStatusEnum.DISPATCHING.getStatus())
                .set(Orders::getPayStatus, OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                .set(Orders::getPayTime, LocalDateTime.now())
                .set(Orders::getTransactionId, tradeStatusMsg.getTransactionId())
                .set(Orders::getTradingChannel, tradeStatusMsg.getTradingChannel())
                .update();
        if(!flag){
            throw new CommonException("更改订单支付状态失败");
        }
    }

    /**
     * 保存订单：解决事务失效
     *
     * @param orders
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void saveOrders(Orders orders) {
        this.save(orders);
    }

    /**
     * 订单id生成：1位业务类型+6位年月日+12位自增序号
     * @return 订单id
     */
    private Long generateOrdersId() {
        Long incrementId = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR);
        // 最前边1位代表订单业务类型：1-用户服务订单
        return Long.valueOf("1" + (DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd" ) * 1000000000000L + incrementId));
    }

}
